package heap;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import chainexception.ChainException;
import bufmgr.BufMgrException;
import bufmgr.BufferPoolExceededException;
import bufmgr.HashEntryNotFoundException;
import bufmgr.HashOperationException;
import bufmgr.InvalidBufferException;
import bufmgr.InvalidFrameNumberException;
import bufmgr.PageNotReadException;
import bufmgr.PagePinnedException;
import bufmgr.PageUnpinnedException;
import bufmgr.ReplacerException;

import diskmgr.DiskMgrException;
import diskmgr.FileIOException;
import diskmgr.InvalidPageNumberException;
import diskmgr.Page;

import global.GlobalConst;
import global.PageId;
import global.RID;
import global.SystemDefs;

public class Heapfile implements HeapFileInterface {

	public static int SIZE = 1;
	private HFPage pages = new HFPage();
	private LinkedList<PageId> freePages = new LinkedList<PageId>();
	private LinkedList<PageId> fullPages = new LinkedList<PageId>();
	private Serialization serializable;
	private int recCnt = 0;

	public Heapfile(String s) throws IOException {
		// TODO Auto-generated constructor stub
		serializable = new Serialization(SystemDefs.JavabaseDBName);
		PageId pId = new PageId();
		Page current = new Page();
		File header = new File(SystemDefs.JavabaseDBName+"Data");
		if (header.exists()) {
			LinkedList<Integer> temp = serializable.getRecNumAndCurr();
			recCnt = temp.get(1);
			PageId p = new PageId(temp.get(0));
			pages.setCurPage(p);
			ArrayList<LinkedList<PageId>> temp1 = serializable
					.returnFreeAndFull();
			setFreePages(temp1.get(0));
			setFullPages(temp1.get(1));
			serializable.delete();
			 //header = new File(SystemDefs.JavabaseDBName+"Data");

		} else
			try {
				pId = SystemDefs.JavabaseBM.newPage(current, 1);
				pages.init(pId, current);
				// System.out.println("-0-----------"+pId.pid);
				freePages.add(pId);
				// System.out.println("-1-----------"+pId.pid);
				SystemDefs.JavabaseBM.unpinPage(pId, false);
				// System.out.println("-2-----------"+pId.pid);
			} catch (BufferPoolExceededException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			} catch (HashOperationException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			} catch (ReplacerException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			} catch (HashEntryNotFoundException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			} catch (InvalidFrameNumberException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			} catch (PagePinnedException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			} catch (PageUnpinnedException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			} catch (PageNotReadException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			} catch (BufMgrException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			} catch (DiskMgrException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}


	}
	public static int getSIZE() {
		return SIZE;
	}

	public static void setSIZE(int size) {
		SIZE = size;
	}

	public HFPage getPages() {
		return pages;
	}

	public void setPages(HFPage pages) {
		this.pages = pages;
	}

	public LinkedList<PageId> getFreePages() {
		return freePages;
	}

	public void setFreePages(LinkedList<PageId> freePages) {
		this.freePages = freePages;
	}

	public LinkedList<PageId> getFullPages() {
		return fullPages;
	}

	public void setFullPages(LinkedList<PageId> fullPages) {
		this.fullPages = fullPages;
	}

	public void setRecCnt(int recCnt) {
		this.recCnt = recCnt;
	}
private void delete(PageId curr) throws InvalidBufferException, ReplacerException, HashOperationException, InvalidFrameNumberException, PageNotReadException, BufferPoolExceededException, PagePinnedException, PageUnpinnedException, HashEntryNotFoundException, BufMgrException, DiskMgrException{
	try {
		pages.setCurPage(curr);
		SystemDefs.JavabaseBM.freePage(curr);
	} catch (IOException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
	
	
}
	@Override
	public void deleteFile(String fileName) throws InvalidBufferException, ReplacerException, HashOperationException, InvalidFrameNumberException, PageNotReadException, BufferPoolExceededException, PagePinnedException, PageUnpinnedException, HashEntryNotFoundException, BufMgrException, DiskMgrException {
		// TODO Auto-generated method stub
		
		
		Iterator<PageId> pagesIdFull = fullPages.iterator();
		while(pagesIdFull.hasNext()){
			PageId curr = pagesIdFull.next();
			delete(curr);
		}
		Iterator<PageId> pagesIdFree = freePages.iterator();
		while(pagesIdFree.hasNext()){
			PageId curr = pagesIdFree.next();
			delete(curr);
		}
		
		fullPages = new LinkedList<PageId>();
		freePages=new LinkedList<PageId>();
		pages = new HFPage();
		recCnt=0;
		
//		Page currPage = new Page();
//		DirectoryPage curDirPage = null;
//		boolean exists = false;
//		PageDirectoryEntry dirEntry = null;
//		PageId nextDirPageId = new PageId();
//		
//		while(!exists && curDirPageId.pid != GlobalConst.INVALID_PAGE){
//			try{
//				SystemDefs.JavabaseBM.pinPage(curDirPageId, currPage, false);
//			}
//			catch(Exception e){
//				throw new HFBufMgrException(e,"Pin Page Failed in deleteFile");
//			}
//			curDirPage = new DirectoryPage(currPage); 
//			
//			for(int i=0; i < curDirPage.getTotalEntries(); i++)
//			{
//				dirEntry = curDirPage.getPageDirectoryEntry(i);
//				
//				// Attempt to free the page referred to by this entry
//				// only if it is a valid non-directory page.
//				if(dirEntry.getPID() != GlobalConst.INVALID_PAGE && 
//						dirEntry.getEntryType() != PageDirectoryEntry.DIRECTORYPAGE_ENTRY){
//					try {
//						SystemDefs.JavabaseBM.freePage(new PageId(dirEntry.getPID()));
//					} catch (Exception e) {
//						throw new HFBufMgrException(e,"Free Page Failed during file delete");
//					} 
//				}
//			}
//			
//			PageId tmp1 = new PageId(curDirPage.getNextDirectory().getPID());
//			nextDirPageId.pid = tmp1.pid;
//			try{
//				SystemDefs.JavabaseBM.unpinPage(curDirPageId, false);
//			}
//			catch(Exception ex){
//				throw new HFBufMgrException(ex, "Unpin Page Failed in deleteFile");
//			}
//			try{
//				
//				SystemDefs.JavabaseBM.freePage(curDirPageId);
//			}
//			catch(Exception e){
//				throw new HFBufMgrException(e,"Free Page Failed for directory page in deleteFile");
//			}
//			curDirPageId = nextDirPageId;
//		}
//		try {
//			SystemDefs.JavabaseDB.delete_file_entry(fileName);
//		} catch (Exception e) {
//			throw new HFDiskMgrException(e, "Error deleting file entry");
//		}
//	}
	}

	@Override
	public boolean deleteRecord(RID rid) {

		PageId pid = rid.pageNo;
		try {
			pages.setCurPage(pid);
			pages.deleteRecord(rid);
			recCnt--;
			writeInfile(pid.pid);
			return true;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvalidSlotNumberException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return false;
	}

	@Override
	public Tuple getRecord(RID rid) {
		if (rid == null) {
			return null;
		}
		PageId pid = rid.pageNo;
		try {
			pages.setCurPage(pid);
			writeInfile(pid.pid);
			return pages.getRecord(rid);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvalidSlotNumberException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return null;
	}

	public int getRecCnt() {

		return recCnt;
	}

	public RID insertRecord(byte[] recPtr) throws ChainException {
		// TODO Auto-generated method stub
		if (recPtr.length > GlobalConst.MINIBASE_PAGESIZE) {
			throw new SpaceNotAvailableException(null,
					"record size is to large");
		}
		PageId pId = null;
		boolean check = true;
		RID rid = null;
		while (check) {
			pId = null;
			if (!freePages.isEmpty()) {
				pId = freePages.pop();

			} else {
				PageId pid = new PageId();
				Page page = new Page();
				try {
					pId = SystemDefs.JavabaseBM.newPage(page, 1);
					pages.init(pId, page);
					SystemDefs.JavabaseBM.unpinPage(pId, false);
				} catch (BufferPoolExceededException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (HashOperationException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (ReplacerException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (HashEntryNotFoundException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (InvalidFrameNumberException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (PagePinnedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (PageUnpinnedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (PageNotReadException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (BufMgrException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (DiskMgrException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}

			try {

				PageId last = pages.getCurPage();
				pages.setPrevPage(last);
				pages.setNextPage(pId);
				pages.setCurPage(pId);
				// Page p = new Page();
				// pages.openHFpage(p);
				SystemDefs.JavabaseBM.pinPage(pId, pages, false);

				if (pages.available_space() >= recPtr.length) {
					rid = pages.insertRecord(recPtr);
					recCnt++;
					SystemDefs.JavabaseBM.unpinPage(pId, true);
					if (pages.available_space() == 0) {
						fullPages.push(pId);
					} else {
						freePages.push(pId);
					}
					check = false;
				} else {

					SystemDefs.JavabaseBM.unpinPage(pId, true);
					fullPages.push(pId);
					// this is the problem of fixed length if the record is
					// fixed length then no problem
					// else we must check for each record to not enter in an
					// infinite loop , I assumed here
					// record is fixed length
				}

			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (ReplacerException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (HashOperationException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (PageUnpinnedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (InvalidFrameNumberException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (PageNotReadException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (BufferPoolExceededException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (PagePinnedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (BufMgrException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (HashEntryNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		writeInfile(pId.pid);
		return rid;
	}

	@Override
	public Scan openScan() {

		Scan scan = new Scan(this);
		return scan;
	}

	@Override
	public boolean updateRecord(RID rid, Tuple newTuple) throws IOException,
			ChainException {
		// PageId pid = rid.pageNo;

		Tuple tuple = new Tuple();
		PageId pa = rid.pageNo;

		pages.setCurPage(pa);

		tuple = pages.getRecord(rid);

		if (newTuple.getLength() != tuple.getLength()) {
			writeInfile(pa.pid);
			throw new InvalidUpdateException(null,
					"heap.InvalidUpdateException");

		} else {
			System.arraycopy(newTuple.getTupleByteArray(), 0, pages
					.getHFpageArray(), pages.getSlotOffset(rid.slotNo),
					newTuple.getTupleByteArray().length);
			writeInfile(pa.pid);
			return true;
		}

	}

	private LinkedList<Object> getObjectList(LinkedList<PageId> pages) {
		LinkedList<Object> l = new LinkedList<Object>();
		for (int i = 0; i < pages.size(); i++) {
			l.add(pages.get(i).pid);
		}
		return l;
	}

	private void writeInfile(int current) {
		ArrayList<LinkedList<Object>> myArray = new ArrayList<LinkedList<Object>>();
		// of zero
		myArray.add(getObjectList(freePages));
		// of one
		myArray.add(getObjectList(fullPages));
		// of two
		LinkedList<Object> temp = new LinkedList<Object>();
		// of zero
		temp.add(current);
		// of one
		temp.add(recCnt);
		myArray.add(temp);
		serializable.write(myArray);
	}
}